#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Core Zen Coding library. Contains various text manipulation functions:

== Expand abbreviation
Expands abbreviation like ul#nav>li*5>a into a XHTML string.
=== How to use
First, you have to extract current string (where cursor is) from your test 
editor and use <code>find_abbr_in_line()</code> method to extract abbreviation. 
If abbreviation was found, this method will return it as well as position index
of abbreviation inside current line. If abbreviation wasn't 
found, method returns empty string. With abbreviation found, you should call
<code>parse_into_tree()</code> method to transform abbreviation into a tag tree. 
This method returns <code>Tag</code> object on success, None on failure. Then
simply call <code>to_string()</code> method of returned <code>Tag</code> object
to transoform tree into a XHTML string

You can setup output profile using <code>setup_profile()</code> method 
(see <code>default_profile</code> definition for available options) 

 
Created on Apr 17, 2009

@author: Sergey Chikuyonok (http://chikuyonok.ru)
'''
from zen_settings import zen_settings
import re
import stparser

newline = '\n'
"Newline symbol"

caret_placeholder = ''

insertion_point = '|'
"Symbol which refers to cursor position"

sub_insertion_point = '|'
"""@deprecated: Symbol which refers to cursor position (for editors which support multiple placeholders)"""

content_placeholder = '{%::zen-content::%}'

re_tag = re.compile(r'<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*\s*(\/?)>$')

profiles = {}

default_profile = {
        'tag_case': 'lower',         # values are 'lower', 'upper'
        'attr_case': 'lower',        # values are 'lower', 'upper'
        'attr_quotes': 'double',     # values are 'single', 'double'
        
        'tag_nl': 'decide',          # each tag on new line, values are True, False, 'decide'
        
        'place_cursor': True,        # place cursor char — | (pipe) — in output
        
        'indent': True,              # indent tags
        
        'inline_break': 3,           # how many inline elements should be to force line break (set to 0 to disable)
        
        'self_closing_tag': 'xhtml'  # use self-closing style for writing empty elements, e.g. <br /> or <br>. 
                                 # values are True, False, 'xhtml'
}

basic_filters = 'html';
"Filters that will be applied for unknown syntax"

def char_at(text, pos):
        """
        Returns character at specified index of text.
        If index if out of range, returns empty string
        """
        return text[pos] if pos < len(text) else ''
        

def has_deep_key(obj, key):
        """
        Check if <code>obj</code> dictionary contains deep key. For example,
        example, it will allow you to test existance of my_dict[key1][key2][key3],
        testing existance of my_dict[key1] first, then my_dict[key1][key2], 
        and finally my_dict[key1][key2][key3]
        @param obj: Dictionary to test
        @param obj: dict
        @param key: Deep key to test. Can be list (like ['key1', 'key2', 'key3']) or
        string (like 'key1.key2.key3')
        @type key: list, tuple, str
        @return: bool
        """
        if isinstance(key, str):
                key = key.split('.')
                
        last_obj = obj
        for v in key:
                if hasattr(last_obj, v):
                        last_obj = getattr(last_obj, v)
                elif last_obj.has_key(v):
                        last_obj = last_obj[v]
                else:
                        return False
        
        return True
                

def is_allowed_char(ch):
        """
        Test if passed symbol is allowed in abbreviation
        @param ch: Symbol to test
        @type ch: str
        @return: bool
        """
        return ch.isalnum() or ch in "#.>+*:$-_!@[]()|"
        

def split_by_lines(text, remove_empty=False):
        """
        Split text into lines. Set <code>remove_empty</code> to true to filter out
        empty lines
        @param text: str
        @param remove_empty: bool
        @return list
        """
        lines = text.splitlines()
        
        return remove_empty and [line for line in lines if line.strip()] or lines

def make_map(prop):
        """
        Helper function that transforms string into dictionary for faster search
        @param prop: Key name in <code>zen_settings['html']</code> dictionary
        @type prop: str
        """
        obj = {}
        for a in zen_settings['html'][prop].split(','):
                obj[a] = True
                
        zen_settings['html'][prop] = obj

def create_profile(options):
        """
        Create profile by adding default values for passed optoin set
        @param options: Profile options
        @type options: dict
        """
        for k, v in default_profile.items():
                options.setdefault(k, v)
        
        return options

def setup_profile(name, options = {}):
        """
        @param name: Profile name
        @type name: str
        @param options: Profile options
        @type options: dict
        """
        profiles[name.lower()] = create_profile(options);

def get_newline():
        """
        Returns newline symbol which is used in editor. This function must be 
        redefined to return current editor's settings 
        @return: str
        """
        return newline

def set_newline(char):
        """
        Sets newline character used in Zen Coding
        """
        global newline
        newline = char

def string_to_hash(text):
        """
        Helper function that transforms string into hash
        @return: dict
        """
        obj = {}
        items = text.split(",")
        for i in items:
                obj[i] = True
                
        return obj

def pad_string(text, pad):
        """
        Indents string with space characters (whitespace or tab)
        @param text: Text to indent
        @type text: str
        @param pad: Indentation level (number) or indentation itself (string)
        @type pad: int, str
        @return: str
        """
        pad_str = ''
        result = ''
        if isinstance(pad, basestring):
                pad_str = pad
        else:
                pad_str = get_indentation() * pad
                
        nl = get_newline()
        
        lines = split_by_lines(text)
        
        if lines:
                result += lines[0]
                for line in lines[1:]:
                        result += nl + pad_str + line
                        
        return result

def is_snippet(abbr, doc_type = 'html'):
        """
        Check is passed abbreviation is a snippet
        @return bool
        """
        return get_snippet(doc_type, abbr) and True or False

def is_ends_with_tag(text):
        """
        Test is string ends with XHTML tag. This function used for testing if '<'
        symbol belogs to tag or abbreviation 
        @type text: str
        @return: bool
        """
        return re_tag.search(text) != None

def get_elements_collection(resource, type):
        """
        Returns specified elements collection (like 'empty', 'block_level') from
        <code>resource</code>. If collections wasn't found, returns empty object
        @type resource: dict
        @type type: str
        @return: dict
        """
        if 'element_types' in resource and type in resource['element_types']:
                return resource['element_types'][type]
        else:
                return {}
        
def replace_variables(text, vars=zen_settings['variables']):
        """
        Replace variables like ${var} in string
        @param text: str
        @return: str
        """
        return re.sub(r'\$\{([\w\-]+)\}', lambda m: m.group(1) in vars and vars[m.group(1)] or m.group(0), text)

def get_abbreviation(res_type, abbr):
        """
        Returns abbreviation value from data set
        @param res_type: Resource type (html, css, ...)
        @type res_type: str
        @param abbr: Abbreviation name
        @type abbr: str
        @return dict, None
        """
        return get_settings_resource(res_type, abbr, 'abbreviations')

def get_snippet(res_type, snippet_name):
        """
        Returns snippet value from data set
        @param res_type: Resource type (html, css, ...)
        @type res_type: str
        @param snippet_name: Snippet name
        @type snippet_name: str
        @return dict, None
        """
        return get_settings_resource(res_type, snippet_name, 'snippets');

def get_variable(name):
        """
        Returns variable value
         @return: str
        """
        return zen_settings['variables'][name]

def set_variable(name, value):
        """
        Set variable value
        """
        zen_settings['variables'][name] = value

def get_indentation():
        """
        Returns indentation string
        @return {String}
        """
        return get_variable('indentation');

def create_resource_chain(syntax, name):
        """
        Creates resource inheritance chain for lookups
        @param syntax: Syntax name
        @type syntax: str
        @param name: Resource name
        @type name: str
        @return: list
        """
        result = []
        
        if syntax in zen_settings:
                resource = zen_settings[syntax]
                if name in resource:
                        result.append(resource[name])
                if 'extends' in resource:
                        # find resource in ancestors
                        for type in resource['extends']:
                                if  has_deep_key(zen_settings, [type, name]):
                                        result.append(zen_settings[type][name])
                                
        return result

def get_resource(syntax, name):
        """
        Get resource collection from settings file for specified syntax. 
        It follows inheritance chain if resource wasn't directly found in
        syntax settings
        @param syntax: Syntax name
        @type syntax: str
        @param name: Resource name
        @type name: str
        """
        chain = create_resource_chain(syntax, name)
        return chain[0] if chain else None

def get_settings_resource(syntax, abbr, name):
        """
        Returns resurce value from data set with respect of inheritance
        @param syntax: Resource syntax (html, css, ...)
        @type syntax: str
        @param abbr: Abbreviation name
        @type abbr: str
        @param name: Resource name ('snippets' or 'abbreviation')
        @type name: str
        @return dict, None
        """
        for item in create_resource_chain(syntax, name):
                if abbr in item:
                        return item[abbr]
                
        return None

def get_word(ix, text):
        """
        Get word, starting at <code>ix</code> character of <code>text</code>
        @param ix: int
        @param text: str
        """
        m = re.match(r'^[\w\-:\$]+', text[ix:])
        return m.group(0) if m else ''
        
def extract_attributes(attr_set):
        """
        Extract attributes and their values from attribute set 
        @param attr_set: str
        """
        attr_set = attr_set.strip()
        loop_count = 100 # endless loop protection
        re_string = r'^(["\'])((?:(?!\1)[^\\]|\\.)*)\1'
        result = []
                
        while attr_set and loop_count:
                loop_count -= 1
                attr_name = get_word(0, attr_set)
                attr = None
                if attr_name:
                        attr = {'name': attr_name, 'value': ''}
                        
                        # let's see if attribute has value
                        ch = attr_set[len(attr_name)] if len(attr_set) > len(attr_name) else ''
                        if ch == '=':
                                ch2 = attr_set[len(attr_name) + 1]
                                if ch2 in '"\'':
                                        # we have a quoted string
                                        m = re.match(re_string, attr_set[len(attr_name) + 1:])
                                        if m:
                                                attr['value'] = m.group(2)
                                                attr_set = attr_set[len(attr_name) + len(m.group(0)) + 1:].strip()
                                        else:
                                                # something wrong, break loop
                                                attr_set = ''
                                else:
                                        # unquoted string
                                        m = re.match(r'^(.+?)(\s|$)', attr_set[len(attr_name) + 1:])
                                        if m:
                                                attr['value'] = m.group(1)
                                                attr_set = attr_set[len(attr_name) + len(m.group(1)) + 1:].strip()
                                        else:
                                                # something wrong, break loop
                                                attr_set = ''
                                
                        else:
                                attr_set = attr_set[len(attr_name):].strip()
                else:
                        # something wrong, can't extract attribute name
                        break
                
                if attr: result.append(attr)
                
        return result

def parse_attributes(text):
        """
        Parses tag attributes extracted from abbreviation
        """
        
#       Example of incoming data:
#       #header
#       .some.data
#       .some.data#header
#       [attr]
#       #item[attr=Hello other="World"].class

        result = []
        class_name = None
        char_map = {'#': 'id', '.': 'class'}
        
        # walk char-by-char
        i = 0
        il = len(text)
                
        while i < il:
                ch = text[i]
                
                if ch == '#': # id
                        val = get_word(i, text[1:])
                        result.append({'name': char_map[ch], 'value': val})
                        i += len(val) + 1
                        
                elif ch == '.': #class
                        val = get_word(i, text[1:])
                        if not class_name:
                                # remember object pointer for value modification
                                class_name = {'name': char_map[ch], 'value': ''}
                                result.append(class_name)
                        
                        if class_name['value']:
                                class_name['value'] += ' ' + val
                        else:
                                class_name['value'] = val
                        
                        i += len(val) + 1
                                
                elif ch == '[': # begin attribute set
                        # search for end of set
                        end_ix = text.find(']', i)
                        if end_ix == -1:
                                # invalid attribute set, stop searching
                                i = len(text)
                        else:
                                result.extend(extract_attributes(text[i + 1:end_ix]))
                                i = end_ix
                else:
                        i += 1
                
                
        return result

class AbbrGroup(object):
        """
        Abreviation's group element
        """
        def __init__(self, parent=None):
                """
                @param parent: Parent group item element
                @type parent: AbbrGroup
                """
                self.expr = ''
                self.parent = parent
                self.children = []
                
        def add_child(self):
                child = AbbrGroup(self)
                self.children.append(child)
                return child
        
        def clean_up(self):
                for item in self.children:
                        expr = item.expr
                        if not expr:
                                self.children.remove(item)
                        else:
                                # remove operators at the and of expression
                                item.clean_up()

def split_by_groups(abbr):
        """
        Split abbreviation by groups
        @type abbr: str
        @return: AbbrGroup
        """
        root = AbbrGroup()
        last_parent = root
        cur_item = root.add_child()
        stack = []
        i = 0
        il = len(abbr)
        
        while i < il:
                ch = abbr[i]
                if ch == '(':
                        # found new group
                        operator = i and abbr[i - 1] or ''
                        if operator == '>':
                                stack.append(cur_item)
                                last_parent = cur_item
                        else:
                                stack.append(last_parent)
                        cur_item = None
                elif ch == ')':
                        last_parent = stack.pop()
                        cur_item = None
                        next_char = char_at(abbr, i + 1)
                        if next_char == '+' or next_char == '>': 
                                # next char is group operator, skip it
                                i += 1
                else:
                        if ch == '+' or ch == '>':
                                # skip operator if it's followed by parenthesis
                                next_char = char_at(abbr, i + 1)
                                if next_char == '(':
                                        i += 1 
                                        continue
                        
                        if not cur_item:
                                cur_item = last_parent.add_child()
                        cur_item.expr += ch
                        
                i += 1
        
        root.clean_up()
        return root

def rollout_tree(tree, parent=None):
        """
        Roll outs basic Zen Coding tree into simplified, DOM-like tree.
        The simplified tree, for example, represents each multiplied element 
        as a separate element sets with its own content, if exists.
         
        The simplified tree element contains some meta info (tag name, attributes, 
        etc.) as well as output strings, which are exactly what will be outputted
        after expanding abbreviation. This tree is used for <i>filtering</i>:
        you can apply filters that will alter output strings to get desired look
        of expanded abbreviation.
         
        @type tree: Tag
        @param parent: ZenNode
        """
        if not parent:
                parent = ZenNode(tree)
                
        how_many = 1
        tag_content = ''
        
        for child in tree.children:
                how_many = child.count
                
                if child.repeat_by_lines:
                        # it's a repeating element
                        tag_content = split_by_lines(child.get_content(), True)
                        how_many = max(len(tag_content), 1)
                else:
                        tag_content = child.get_content()
                
                for j in range(how_many):
                        tag = ZenNode(child)
                        parent.add_child(tag)
                        
                        if child.children:
                                rollout_tree(child, tag)
                                
                        add_point = tag.find_deepest_child() or tag
                        
                        if tag_content:
                                if isinstance(tag_content, basestring):
                                        add_point.content = tag_content
                                else:
                                        add_point.content = tag_content[j] or ''
                                        
        return parent

def run_filters(tree, profile, filter_list):
        """
        Runs filters on tree
        @type tree: ZenNode
        @param profile: str, object
        @param filter_list: str, list
        @return: ZenNode
        """
        import filters
        
        if isinstance(profile, basestring) and profile in profiles:
                profile = profiles[profile];
        
        if not profile:
                profile = profiles['plain']
                
        if isinstance(filter_list, basestring):
                filter_list = re.split(r'[\|,]', filter_list)
                
        for name in filter_list:
                name = name.strip()
                if name and name in filters.filter_map:
                        tree = filters.filter_map[name](tree, profile)
                        
        return tree

def abbr_to_primary_tree(abbr, doc_type='html'):
        """
        Transforms abbreviation into a primary internal tree. This tree should'n 
        be used ouside of this scope
        @param abbr: Abbreviation to transform
        @type abbr: str
        @param doc_type: Document type (xsl, html), a key of dictionary where to
        search abbreviation settings
        @type doc_type: str
        @return: Tag
        """
        root = Tag('', 1, doc_type)
        token = re.compile(r'([\+>])?([a-z@\!\#\.][a-z0-9:\-]*)((?:(?:[#\.][\w\-\$]+)|(?:\[[^\]]+\]))+)?(\*(\d*))?(\+$)?', re.IGNORECASE)
        
        if not abbr:
                return None
        
        def expando_replace(m):
                ex = m.group(0)
                a = get_abbreviation(doc_type, ex)
                return a and a.value or ex
                
        def token_expander(operator, tag_name, attrs, has_multiplier, multiplier, has_expando):
                multiply_by_lines = (has_multiplier and not multiplier)
                multiplier = multiplier and int(multiplier) or 1
                
                tag_ch = tag_name[0]
                if tag_ch == '#' or tag_ch == '.':
                        if attrs: attrs = tag_name + attrs
                        else: attrs = tag_name
                        tag_name = default_tag
                
                if has_expando:
                        tag_name += '+'
                
                current = is_snippet(tag_name, doc_type) and Snippet(tag_name, multiplier, doc_type) or Tag(tag_name, multiplier, doc_type)
                
                if attrs:
                        attrs = parse_attributes(attrs)
                        for attr in attrs:
                                current.add_attribute(attr['name'], attr['value'])
                        
                # dive into tree
                if operator == '>' and token_expander.last:
                        token_expander.parent = token_expander.last;
                        
                token_expander.parent.add_child(current)
                token_expander.last = current
                
                if multiply_by_lines:
                        root.multiply_elem = current
                
                return ''
                
        # replace expandos
        abbr = re.sub(r'([a-z][a-z0-9]*)\+$', expando_replace, abbr)
        
        token_expander.parent = root
        token_expander.last = None
        
        
#       abbr = re.sub(token, lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6), m.group(7)), abbr)
        # Issue from Einar Egilsson
        abbr = token.sub(lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6)), abbr)
        
        root.last = token_expander.last
        
        # empty 'abbr' variable means that abbreviation was expanded successfully, 
        # non-empty variable means there was a syntax error
        return not abbr and root or None;

def expand_group(group, doc_type, parent):
        """
        Expand single group item 
        @param group: AbbrGroup
        @param doc_type: str
        @param parent: Tag
        """
        tree = abbr_to_primary_tree(group.expr, doc_type)
        last_item = None
                
        if tree:
                for item in tree.children:
                        last_item = item
                        parent.add_child(last_item)
        else:
                raise Exception('InvalidGroup')
        
        
        # set repeating element to the topmost node
        root = parent
        while root.parent:
                root = root.parent
        
        root.last = tree.last
        if tree.multiply_elem:
                root.multiply_elem = tree.multiply_elem
                
        # process child groups
        if group.children:
                add_point = last_item.find_deepest_child() or last_item
                for child in group.children:
                        expand_group(child, doc_type, add_point)

def replace_unescaped_symbol(text, symbol, replace):
        """
        Replaces unescaped symbols in <code>text</code>. For example, the '$' symbol
        will be replaced in 'item$count', but not in 'item\$count'.
        @param text: Original string
        @type text: str
        @param symbol: Symbol to replace
        @type symbol: st
        @param replace: Symbol replacement
        @type replace: str, function 
        @return: str
        """
        i = 0
        il = len(text)
        sl = len(symbol)
        match_count = 0
                
        while i < il:
                if text[i] == '\\':
                        # escaped symbol, skip next character
                        i += sl + 1
                elif text[i:i + sl] == symbol:
                        # have match
                        cur_sl = sl
                        match_count += 1
                        new_value = replace
                        if callable(new_value):
                                replace_data = replace(text, symbol, i, match_count)
                                if replace_data:
                                        cur_sl = len(replace_data[0])
                                        new_value = replace_data[1]
                                else:
                                        new_value = False
                        
                        if new_value is False: # skip replacement
                                i += 1
                                continue
                        
                        text = text[0:i] + new_value + text[i + cur_sl:]
                        # adjust indexes
                        il = len(text)
                        i += len(new_value)
                else:
                        i += 1
        
        return text
        
def run_action(name, *args, **kwargs):
        """
         Runs Zen Coding action. For list of available actions and their
         arguments see zen_actions.py file.
         @param name: Action name 
         @type name: str 
         @param args: Additional arguments. It may be array of arguments
         or inline arguments. The first argument should be <code>zen_editor</code> instance
         @type args: list
         @example
         zen_coding.run_actions('expand_abbreviation', zen_editor)
         zen_coding.run_actions('wrap_with_abbreviation', zen_editor, 'div')  
        """
        import zen_actions
        
        try:
                if hasattr(zen_actions, name):
                        return getattr(zen_actions, name)(*args, **kwargs)
        except:
                return False

def expand_abbreviation(abbr, syntax='html', profile_name='plain'):
        """
        Expands abbreviation into a XHTML tag string
        @type abbr: str
        @return: str
        """
        tree_root = parse_into_tree(abbr, syntax);
        if tree_root:
                tree = rollout_tree(tree_root)
                apply_filters(tree, syntax, profile_name, tree_root.filters)
                return replace_variables(tree.to_string())
        
        return ''

def extract_abbreviation(text):
        """
        Extracts abbreviations from text stream, starting from the end
        @type text: str
        @return: Abbreviation or empty string
        """
        cur_offset = len(text)
        start_index = -1
        brace_count = 0
        
        while True:
                cur_offset -= 1
                if cur_offset < 0:
                        # moved at string start
                        start_index = 0
                        break
                
                ch = text[cur_offset]
                
                if ch == ']':
                        brace_count += 1
                elif ch == '[':
                        brace_count -= 1
                else:
                        if brace_count: 
                                # respect all characters inside attribute sets
                                continue
                        if not is_allowed_char(ch) or (ch == '>' and is_ends_with_tag(text[0:cur_offset + 1])):
                                # found stop symbol
                                start_index = cur_offset + 1
                                break
                
        return text[start_index:] if start_index != -1 else ''

def parse_into_tree(abbr, doc_type='html'):
        """
        Parses abbreviation into a node set
        @param abbr: Abbreviation to transform
        @type abbr: str
        @param doc_type: Document type (xsl, html), a key of dictionary where to
        search abbreviation settings
        @type doc_type: str
        @return: Tag
        """
        # remove filters from abbreviation
        filter_list = []
        
        def filter_replace(m):
                filter_list.append(m.group(1))
                return ''
        
        re_filter = re.compile(r'\|([\w\|\-]+)$')
        abbr = re_filter.sub(filter_replace, abbr)
        
        # split abbreviation by groups
        group_root = split_by_groups(abbr)
        tree_root = Tag('', 1, doc_type)
        
        # then recursively expand each group item
        try:
                for item in group_root.children:
                        expand_group(item, doc_type, tree_root)
        except:
                # there's invalid group, stop parsing
                return None
        
        tree_root.filters = ''.join(filter_list)
        return tree_root

def is_inside_tag(html, cursor_pos):
        re_tag = re.compile(r'^<\/?\w[\w\:\-]*.*?>')
        
        # search left to find opening brace
        pos = cursor_pos
        while pos > -1:
                if html[pos] == '<': break
                pos -= 1
        
        
        if pos != -1:
                m = re_tag.match(html[pos:]);
                if m and cursor_pos > pos and cursor_pos < pos + len(m.group(0)):
                        return True

        return False

def wrap_with_abbreviation(abbr, text, doc_type='html', profile='plain'):
        """
        Wraps passed text with abbreviation. Text will be placed inside last
        expanded element
        @param abbr: Abbreviation
        @type abbr: str
        
        @param text: Text to wrap
        @type text: str
        
        @param doc_type: Document type (html, xml, etc.)
        @type doc_type: str
        
        @param profile: Output profile's name.
        @type profile: str
        @return {String}
        """
        tree_root = parse_into_tree(abbr, doc_type)
        if tree_root:
                repeat_elem = tree_root.multiply_elem or tree_root.last
                repeat_elem.set_content(text)
                repeat_elem.repeat_by_lines = bool(tree_root.multiply_elem)
                
                tree = rollout_tree(tree_root)
                apply_filters(tree, doc_type, profile, tree_root.filters);
                return replace_variables(tree.to_string())
        
        return None

def get_caret_placeholder():
        """
        Returns caret placeholder
        @return: str
        """
        if callable(caret_placeholder):
                return caret_placeholder()
        else:
                return caret_placeholder

def set_caret_placeholder(value):
        """
        Set caret placeholder: a string (like '|') or function.
        You may use a function as a placeholder generator. For example,
        TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching
        between them.
        @param {String|Function}
        """
        global caret_placeholder
        caret_placeholder = value

def apply_filters(tree, syntax, profile, additional_filters=None):
        """
        Applies filters to tree according to syntax
        @param tree: Tag tree to apply filters to
        @type tree: ZenNode
        @param syntax: Syntax name ('html', 'css', etc.)
        @type syntax: str
        @param profile: Profile or profile's name
        @type profile: str, object
        @param additional_filters: List or pipe-separated string of additional filters to apply
        @type additional_filters: str, list 
         
        @return: ZenNode
        """
        _filters = get_resource(syntax, 'filters') or basic_filters
                
        if additional_filters:
                _filters += '|'
                if isinstance(additional_filters, basestring):
                        _filters += additional_filters
                else:
                        _filters += '|'.join(additional_filters)
                
        if not _filters:
                # looks like unknown syntax, apply basic filters
                _filters = basic_filters
                
        return run_filters(tree, profile, _filters)

def replace_counter(text, value):
        """
         Replaces '$' character in string assuming it might be escaped with '\'
         @type text: str
         @type value: str, int
         @return: str
        """
        symbol = '$'
        value = str(value)
        
        def replace_func(tx, symbol, pos, match_num):
                if tx[pos + 1] == '{':
                        # it's a variable, skip it
                        return False
                
                # replace sequense of $ symbols with padded number  
                j = pos + 1
                while tx[j] == '$' and char_at(tx, j + 1) != '{': j += 1
                return (tx[pos:j], value.zfill(j - pos))
        
        return replace_unescaped_symbol(text, symbol, replace_func)

def get_profile(name):
        """
        Get profile by it's name. If profile wasn't found, returns 'plain' profile
        """
        return profiles[name] if name in profiles else profiles['plain']

def update_settings(settings):
        globals()['zen_settings'] = settings
        
class Tag(object):
        def __init__(self, name, count=1, doc_type='html'):
                """
                @param name: Tag name
                @type name: str
                @param count:  How many times this tag must be outputted
                @type count: int
                @param doc_type: Document type (xsl, html)
                @type doc_type: str
                """
                #name = name.lower()
                
                abbr = get_abbreviation(doc_type, name)
                
                if abbr and abbr.type == stparser.TYPE_REFERENCE:
                        abbr = get_abbreviation(doc_type, abbr.value)
                
                self.name = abbr and abbr.value['name'] or name.replace('+', '')
                self.count = count
                self.children = []
                self.attributes = []
                self.multiply_elem = None
                self.__attr_hash = {}
                self._abbr = abbr
                self.__content = ''
                self.repeat_by_lines = False
                self._res = zen_settings.has_key(doc_type) and zen_settings[doc_type] or {}
                self.parent = None
                
                # add default attributes
                if self._abbr and 'attributes' in self._abbr.value:
                        for a in self._abbr.value['attributes']:
                                self.add_attribute(a['name'], a['value'])
                
        def add_child(self, tag):
                """
                Add new child
                @type tag: Tag
                """
                tag.parent = self
                self.children.append(tag)
                
        def add_attribute(self, name, value):
                """
                Add attribute to tag. If the attribute with the same name already exists,
                it will be overwritten, but if it's name is 'class', it will be merged
                with the existed one
                @param name: Attribute nama
                @type name: str
                @param value: Attribute value
                @type value: str
                """
                
                # the only place in Tag where pipe (caret) character may exist
                # is the attribute: escape it with internal placeholder
                value = replace_unescaped_symbol(value, '|', get_caret_placeholder());
                
                if name in self.__attr_hash:
#                       attribue already exists
                        a = self.__attr_hash[name]
                        if name == 'class':
#                               'class' is a magic attribute
                                if a['value']:
                                        value = ' ' + value
                                a['value'] += value
                        else:
                                a['value'] = value
                else:
                        a = {'name': name, 'value': value}
                        self.__attr_hash[name] = a
                        self.attributes.append(a)
        
        def has_tags_in_content(self):
                """
                This function tests if current tags' content contains XHTML tags. 
                This function is mostly used for output formatting
                """
                return self.get_content() and re_tag.search(self.get_content())
        
        def get_content(self):
                return self.__content
        
        def set_content(self, value):
                self.__content = value
                
        def set_content(self, content): #@DuplicatedSignature
                self.__content = content
                
        def get_content(self): #@DuplicatedSignature
                return self.__content
        
        def find_deepest_child(self):
                """
                Search for deepest and latest child of current element.
                Returns None if there's no children
                @return Tag or None 
                """
                if not self.children:
                        return None
                        
                deepest_child = self
                while True:
                        deepest_child = deepest_child.children[-1]
                        if not deepest_child.children:
                                break
                
                return deepest_child
        
class Snippet(Tag):
        def __init__(self, name, count=1, doc_type='html'):
                super(Snippet, self).__init__(name, count, doc_type)
                self.value = replace_unescaped_symbol(get_snippet(doc_type, name), '|', get_caret_placeholder())
                self.attributes = {'id': get_caret_placeholder(), 'class': get_caret_placeholder()}
                self._res = zen_settings[doc_type]              
        
        def is_block(self):
                return True
        
class ZenNode(object):
        """
        Creates simplified tag from Zen Coding tag
        """
        def __init__(self, tag):
                """
                @type tag: Tag
                """
                self.type = 'snippet' if isinstance(tag, Snippet) else 'tag'
                self.name = tag.name
                self.attributes = tag.attributes
                self.children = [];
                
                self.source = tag
                "Source element from which current tag was created"
                
                # relations
                self.parent = None
                self.next_sibling = None
                self.previous_sibling = None
                
                # output params
                self.start = ''
                self.end = ''
                self.content = ''
                self.padding = ''

        def add_child(self, tag):
                """
                @type tag: ZenNode
                """
                tag.parent = self
                
                if self.children:
                        last_child = self.children[-1]
                        tag.previous_sibling = last_child
                        last_child.next_sibling = tag
                
                self.children.append(tag)
                
        def get_attribute(self, name):
                """
                Get attribute's value.
                @type name: str
                @return: None if attribute wasn't found
                """
                #name = name.lower()
                for attr in self.attributes:
                        #if attr['name'].lower() == name:
                        return attr['value']
                
                return None
        
        def is_unary(self):
                """
                Test if current tag is unary (no closing tag)
                @return: bool
                """
                if self.type == 'snippet':
                        return False
                        
                return (self.source._abbr and self.source._abbr.value['is_empty']) or (self.name in get_elements_collection(self.source._res, 'empty'))
        
        def is_inline(self):
                """
                Test if current tag is inline-level (like <strong>, <img>)
                @return: bool
                """
                return self.name in get_elements_collection(self.source._res, 'inline_level')
        
        def is_block(self):
                """
                Test if current element is block-level
                @return: bool
                """
                return self.type == 'snippet' or not self.is_inline()
        
        def has_tags_in_content(self):
                """
                This function tests if current tags' content contains xHTML tags. 
                This function is mostly used for output formatting
                """
                return self.content and re_tag.search(self.content)
        
        def has_children(self):
                """
                Check if tag has child elements
                @return: bool
                """
                return bool(self.children)
        
        def has_block_children(self):
                """
                Test if current tag contains block-level children
                @return: bool
                """
                if self.has_tags_in_content() and self.is_block():
                        return True
                
                for item in self.children:
                        if item.is_block():
                                return True
                        
                return False
        
        def find_deepest_child(self):
                """
                Search for deepest and latest child of current element
                Returns None if there's no children
                @return: ZenNode|None 
                """
                if not self.children:
                        return None
                        
                deepest_child = self
                while True:
                        deepest_child = deepest_child.children[-1]
                        if not deepest_child.children:
                                break
                
                return deepest_child
        
        def to_string(self):
                "@return {String}"
                content = ''.join([item.to_string() for item in self.children])
                return self.start + self.content + content + self.end
	
		
# create default profiles
setup_profile('xhtml');
setup_profile('html', {'self_closing_tag': False});
setup_profile('xml', {'self_closing_tag': True, 'tag_nl': True});
setup_profile('plain', {'tag_nl': False, 'indent': False, 'place_cursor': False});

# This method call explicity loads default settings from zen_settings.py on start up
# Comment this line if you want to load data from other resources (like editor's 
# native snippet) 
update_settings(stparser.get_settings())